Skip to content

[web] backing fields for inputs are actually contenteditable dom and spans#3167

Open
Shagen Ogandzhanian (Schahen) wants to merge 6 commits into
jb-mainfrom
sh/web_contenteditable_backing_input
Open

[web] backing fields for inputs are actually contenteditable dom and spans#3167
Shagen Ogandzhanian (Schahen) wants to merge 6 commits into
jb-mainfrom
sh/web_contenteditable_backing_input

Conversation

@Schahen

@Schahen Shagen Ogandzhanian (Schahen) commented Jun 29, 2026

Copy link
Copy Markdown

The goal of this PR is to use as backing DOM entities divs and spans (rather than textareas and inputs)

Testing

manual + ./gradlew testWeb

Release Notes

N/A

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates Compose Web text input so the backing DOM elements are contenteditable span/div nodes (instead of input/textarea), aligning DOM selection/input handling with the new strategy and updating the affected Web tests and styling.

Changes:

  • Replace backing field DOM elements with contenteditable span (single-line) and div (multi-line), and update selection tracking to use the DOM Selection API.
  • Adjust input event processing to use textRangeCollapsed and centralize selection-command creation.
  • Update Web tests and backing-field CSS to target the new .compose-backing-field elements and expected behavior.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/TextFieldFocusTest.kt Updates single-line backing field lookup from input to span.compose-backing-field.
compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/specs/TextFieldTestSpec.kt Updates test helpers to use div.compose-backing-field instead of textarea.
compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/specs/CompositeInputTestSpec.kt Updates composite input test to assert backing field is a div.
compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/MouseTextInputTests.kt Updates hit-testing to use the new backing div.compose-backing-field.
compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/input/ExternalSelectionChangeListenerTest.kt Updates selection-change test to use div backing field and new selection-range helper.
compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/ComposeWindow.web.kt Adjusts backing-field CSS (overflow/white-space) for contenteditable behavior.
compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/NativeInputEventsProcessor.kt Updates selection deletion/insert logic to use textRangeCollapsed and a selection helper.
compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/DomInputStrategy.kt Core migration to contenteditable backing fields and Selection API-based range handling.
Comments suppressed due to low confidence (1)

compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/DomInputStrategy.kt:89

  • beforeinput relies on latestSelection, but latestSelection is never updated when Compose programmatically sets selection via updateState(). If a selectionchange event doesn’t arrive (or is skipped due to pauseSelectionChangeListener), textRangeStart/textRangeEnd can become stale and input events may be processed with incorrect selection offsets. Update latestSelection when calling setSelectionRange() in updateState().
        if (needsTextUpdate || needsSelectionUpdate) {
            pauseSelectionChangeListener = true
            htmlInput.setSelectionRange(textFieldValue.selection.min, textFieldValue.selection.max)
            pauseSelectionChangeListener = false
        }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/DomInputStrategy.kt Outdated
Comment thread compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/DomInputStrategy.kt Outdated
Comment on lines +208 to 210
if (!textRangeCollapsed) {
add(SetSelectionCommand(textRangeStart, textRangeEnd))
}
Comment on lines +73 to +76
val backintInput = getShadowRoot().querySelector("div.compose-backing-field")
assertIs<HTMLDivElement>(backintInput)

val textAreaRect = textArea.getBoundingClientRect()
val textAreaRect = backintInput.getBoundingClientRect()
Comment on lines 22 to +26
import androidx.compose.ui.OnCanvasTests
import androidx.compose.ui.WebApplicationScope
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.setSelectionRange

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Comment on lines +88 to 92
if (needsTextUpdate || needsSelectionUpdate) {
pauseSelectionChangeListener = true
htmlInput.setSelectionRange(textFieldValue.selection.min, textFieldValue.selection.max)
setSelectionRange(htmlInput,textFieldValue.selection.min, textFieldValue.selection.max)
pauseSelectionChangeListener = false
}
Comment on lines +33 to +36
import kotlin.js.length
import kotlin.js.toInt
import kotlin.js.toJsArray
import kotlin.js.toJsNumber
Comment on lines +42 to +43
import org.w3c.dom.Node
import org.w3c.dom.Range
}
})

htmlInput.addEventListener("compositionstart", {evt ->
Comment on lines +208 to 210
if (!textRangeCollapsed) {
add(SetSelectionCommand(textRangeStart, textRangeEnd))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants